<template>
  <div class="universal-node" :class="[nodeTypeClass, { 'is-selected': selected }]">
    <!-- Node Handle (for connecting edges) -->
    <Handle type="target" :position="targetHandlePosition" />
    <Handle type="source" :position="sourceHandlePosition" />

    <!-- Node Content -->
    <div class="node-content" :style="nodeComputedStyles">
      <!-- Icon based on type -->
      <div class="node-icon">
        <el-icon>
          <component :is="nodeIcon" />
        </el-icon>
      </div>

      <!-- Node Label/Name -->
      <div class="node-label">
        <strong>{{ nodeDisplayName }}</strong>
      </div>

      <!-- Separator if both name and value exist -->
      <div v-if="data.name && displayValue !== null" class="separator">:</div>

      <!-- Node Value (for 'value' type or optionally others) -->
      <div v-if="displayValue !== null" class="node-value">
        {{ displayValue }}
      </div>

      <!-- Wrapper for attribute display & separator -->
      <div
        v-if="canShowAttributes" 
        class="attribute-display-wrapper" 
        :style="attributeSeparatorStyle"
        :class="{ 'normalize-height': visualizerSettings.normalizeAppearanceWithAttrs }"
      >
        <!-- Corrected conditional rendering for attributes -->
        <!-- Mode: Count -->
        <span v-if="visualizerSettings.attributeDisplayMode === 'count'" class="attribute-summary">
          (Attrs: {{ attributeCount }})
        </span>
        <!-- Mode: First N or All (using the corrected computed property) -->
        <div v-else-if="shouldRenderAttributeList" class="attribute-list">
          <div v-for="attr in attributesToDisplay" :key="attr.name" class="attribute-item">
            <span class="attr-name">{{ attr.name }}</span>
            <span class="attr-value" :title="attr.value">{{ truncate(attr.value, 15) }}</span>
          </div>
          <span v-if="hasMoreAttributes" class="more-attrs">...</span>
        </div>
      </div>

      <!-- TODO: Add indicators for children, expansion toggle -->

    </div>

    <!-- Node Toolbar -->
    <NodeToolbar 
      :is-visible="selected && appConfig.enableNodeToolbar" 
      :node-id="id" 
      :position="Position.Top"
    >
      <el-button
          type="primary"
          size="small"
          :icon="InfoFilled"
          circle
          @click="handleShowDetails"
          title="查看详情"
       />
      <el-button
          type="danger"
          size="small"
          :icon="Delete"
          circle
          @click="handleDeleteNode"
          title="删除节点"
       />
    </NodeToolbar>
  </div>
</template>

<script setup lang="ts">
import { computed, type CSSProperties } from 'vue';
import { Handle, Position, type NodeProps } from '@vue-flow/core';
import { NodeToolbar } from '@vue-flow/node-toolbar';
import appConfig from '@/config';
import { useUniversalEditorStore } from '@/stores/universalEditorStore';
import { useUniversalVisualizerSettingsStore } from '@/stores/universalVisualizerSettingsStore';
import type { UniversalFlowNodeData, NodeSemanticType } from '@/models/universalFlow';
import { ElIcon, ElButton, ElMessageBox, ElMessage } from 'element-plus';
import {
  Document,
  Folder,
  Files,
  CollectionTag,
  ChatDotSquare,
  QuestionFilled,
  InfoFilled,
  Delete,
  Cpu,
  DataLine,
  Operation,
  Tickets,
  Share,
  RefreshRight,
  Switch
} from '@element-plus/icons-vue';
import {
  semanticTypeColorCodes,
  jdfCoreTagColorCodes,
  defaultElementObjectColor,
  levelColorPalette
} from '@/config';
import tinycolor from 'tinycolor2';
import { getValueFromPath as getValueFromPathUtil } from '@/utils/objectPathUtilsV2';

// Define props
interface UniversalNodeProps extends NodeProps {
  data: UniversalFlowNodeData;
}
const props = defineProps<UniversalNodeProps>();

// --- ADD SETUP LOG ---
console.log(`[UniversalNode] Setup executed for node ID: ${props.id}, Name: ${props.data?.name}`);

// Get settings store instance
const visualizerSettings = useUniversalVisualizerSettingsStore();
const editorStore = useUniversalEditorStore();

// --- NEW Computed Properties for Handle Positions ---
const targetHandlePosition = computed(() => {
  const mode = visualizerSettings.handlePositionMode;
  const direction = visualizerSettings.layoutDirection;
  if (mode === 'top-bottom') return Position.Top;
  if (mode === 'left-right') return Position.Left;
  // mode === 'auto'
  return direction === 'TB' ? Position.Top : Position.Left;
});

const sourceHandlePosition = computed(() => {
  const mode = visualizerSettings.handlePositionMode;
  const direction = visualizerSettings.layoutDirection;
  if (mode === 'top-bottom') return Position.Bottom;
  if (mode === 'left-right') return Position.Right;
  // mode === 'auto'
  return direction === 'TB' ? Position.Bottom : Position.Right;
});

// --- Helper Functions --- 
// --- UPDATED formatDisplayValue --- //
const formatDisplayValue = (value: any): string => {
    if (value === null) return 'null';
    if (value === undefined) return 'undefined';

    const type = typeof value;

    if (type === 'string') {
        // Truncate long strings
        const truncated = value.length > 30 ? value.substring(0, 27) + '...' : value;
        return `"${truncated}"`; // Add quotes
    }
    if (type === 'number' || type === 'boolean') {
        return String(value);
    }
    if (type === 'bigint') {
        return value.toString() + 'n'; // Add 'n' suffix for BigInt
    }

    if (type === 'object') {
        // Check for our custom deserialized types first
        if (value.__universalType__ === 'Function') {
            // Display function marker, maybe extract name if possible from source?
            const source = value.source || '';
            const match = source.match(/^(?:async\s+)?function\*?\s+([a-zA-Z0-9_$]+)\s*\(/);
            const name = match ? match[1] : '';
            return `[Function${name ? ': ' + name : ''}]`;
        }

        // Check for standard complex types
        if (value instanceof Date) {
            try {
                // Use a shorter, more readable format for node display
                return value.toISOString().split('T')[0]; // Just the date part
            } catch { return '[Invalid Date]'; }
        }
        if (value instanceof Map) {
            return `Map(${value.size})`; // Show Map size
        }
        if (value instanceof Set) {
            return `Set(${value.size})`; // Show Set size
        }
        if (value instanceof RegExp) {
             // Truncate long regex source
            const source = value.source;
            const truncatedSource = source.length > 25 ? source.substring(0, 22) + '...' : source;
            return `/${truncatedSource}/${value.flags}`;
        }
        if (value instanceof Error) {
            return `[Error: ${value.message.substring(0, 20)}${value.message.length > 20 ? '...' : ''}]`;
        }
        if (value instanceof ArrayBuffer) {
            return `ArrayBuffer(${value.byteLength})`;
        }
        if (ArrayBuffer.isView(value)) { // TypedArrays
             return `${value.constructor.name}(${value.byteLength})`;
        }

        // Fallback for other objects (plain objects, arrays)
        // Show simple preview, truncate if long
        if (Array.isArray(value)) {
            return `[Array(${value.length})]`;
        }
        try {
            // Basic stringify for plain objects, limited depth?
            // JSON.stringify has limitations (cycles, other types)
            // Might be better to just show [Object]
            return '{Object}';
            // const jsonStr = JSON.stringify(value);
            // return jsonStr.length > 30 ? jsonStr.substring(0, 27) + '...' : jsonStr;
        } catch (e) {
            return '{Object}'; // Fallback for circular or complex objects
        }
    }

    // Fallback for Symbols and other unexpected types
    if (type === 'symbol') {
        return value.toString();
    }

    // Absolute fallback
    try {
        return String(value).substring(0, 30); // Limit length
    } catch {
        return '[?]';
    }
};
// --- END UPDATED formatDisplayValue --- //

function getValueFromPath(obj: any, path: string): any {
    if (!obj || typeof path !== 'string' || path === '') {
        return undefined;
    }
    try {
        const parts = path.match(/[^\.[\]]+|\[[0-9]+\]/g);
        if (!parts) {
            if (typeof obj === 'object' && obj !== null && path in obj) {
                return obj[path];
            } else {
                 console.warn(`[getValueFromPath] Path "${path}" could not be parsed or does not exist directly in root object.`);
                 return undefined;
            }
        }

        let current = obj;
        for (const part of parts) {
            if (current === null || current === undefined) {
                return undefined;
            }

            let key: string | number;
            if (part.startsWith('[') && part.endsWith(']')) {
                key = parseInt(part.substring(1, part.length - 1), 10);
                if (isNaN(key)) { 
                    console.warn(`[getValueFromPath] Invalid array index found in path "${path}": ${part}`);
                    return undefined; 
                }
            } else {
                key = part;
            }

            if (typeof current !== 'object' || current === null || !(key in current)) {
                 console.warn(`[getValueFromPath] Key/Index "${key}" (from part "${part}") not found in path "${path}". Current object:`, current); 
                return undefined;
            }

            current = current[key];
        }
        // --- ADDED: Check if the final value is our Function marker ---
        // This prevents showing the marker object itself as the value
        if (current && typeof current === 'object' && current.__universalType__ === 'Function') {
            return current; // Return the marker itself for the formatter to handle
        }
        // --- END ADDED ---

        return current;
    } catch (error) {
        console.warn(`[UniversalNode] Error accessing path "${path}" in object:`, error);
        return undefined;
    }
}

// --- UPDATED fallbackDisplayValue (simpler now) --- //
const fallbackDisplayValue = computed(() => {
    const type = props.data.nodeSemanticType;
    const meta = props.data.metadata;
    const value = props.data.value; // This is the *original* value from parser, not evaluated one

    // Explicitly return null for meta nodes
    if (type === 'sourceSnippet' || type === 'evaluatedResult') {
        return null;
    }

    // Priority 1: Actual primitive value from parser (for non-JS/TS or simple values)
    if (type === 'value') {
        return formatDisplayValue(value); // Format even the parsed value
    }
    // Priority 2: Comment content
    if (type === 'comment' && meta?.comment) {
        const commentStr = Array.isArray(meta.comment) ? meta.comment[0] : meta.comment;
        const truncated = commentStr.length > 30 ? commentStr.substring(0, 27) + '...' : commentStr;
        return truncated; // Don't add quotes to comments
    }

    // Priority 3: Generate placeholders for specific JS/TS AST node types
    // This is shown if evaluateConfig lookup fails
    switch (type) {
        case 'functionCall': return `[Call: ${meta?.functionName || 'fn'}()]`;
        case 'variableReference': return `[Var: ${meta?.variableName || 'var'}]`;
        case 'memberExpression': return `[Member Access]`;
        case 'binaryExpression': return `[Op: ${meta?.operator || 'op'}]`;
        case 'unaryExpression': return `[Op: ${meta?.operator || 'op'}]`;
        case 'conditionalExpression': return `[Condition ?:]`;
        case 'templateLiteral': return `[Template \`\`]`;
        case 'newExpression': return `[New: ${meta?.constructorName || 'obj'}]`;
        // Add more AST types if needed
    }

    // Priority 4: Return null for container types (Object, Array, Element, Root)
    // Let the node label (name) be the primary info
    if (type === 'object' || type === 'array' || type === 'root' || type === 'element') {
        return null;
    }

    // Final fallback if none of the above match
    return null;
});
// --- END UPDATED fallbackDisplayValue --- //

// --- displayValue (Logic remains similar, uses new helpers) --- //
const displayValue = computed(() => {
    const evaluatedConfig = editorStore.evaluatedConfig;
    const path = props.data.path;
    const format = props.data.format;

    // Log remains useful for debugging
    // console.log(`[UniversalNode ${props.id}] Computing displayValue. Path: "${path}". Evaluated Config available: ${!!evaluatedConfig}`);

    // Priority 1: Try getting value from evaluated config for JS/TS ONLY IF path exists
    if (evaluatedConfig && (format === 'javascript' || format === 'typescript') && path !== null) {
        // Check for execution errors reported by the deserializer
        if (evaluatedConfig.__universalExecutionError__) {
             // console.warn(`[UniversalNode ${props.id}] Skipping evaluated value due to QuickJS execution error.`); // Keep this warn commented for now
             // Optionally display an error indicator on the node?
             // return '[Exec Error]';
        } else {
            // --- Use Renamed V2 function --- 
            const valueAtPath = getValueFromPathUtil(evaluatedConfig, path);
            // -------------------------------
            // Check if value was found (getValueFromPath returns undefined if not found)
            if (valueAtPath !== undefined) {
                // Use the enhanced formatter
                return formatDisplayValue(valueAtPath);
            }
            // Value not found at path in evaluated config, log and fall through to fallback
            // REMOVED the warning log here, as fallback is now the expected behavior for many nodes
            // console.warn(`[UniversalNode ${props.id}] Value not found at path "${path}" in evaluated config. Using fallback display.`);\n        }\n    }\n\n    // Priority 2: Return the fallback display value (based on AST node type/parsed value, or if path is null)\n    return fallbackDisplayValue.value;\n});\n\n// ... rest of the component ...\n
        }
    }

    // Priority 2: Return the fallback display value (based on AST node type/parsed value, or if path is null)
    return fallbackDisplayValue.value;
});

// --- Computed Properties for Styling/Structure (Keep as is) ---
const nodeIcon = computed(() => {
  switch (props.data.nodeSemanticType) {
    case 'root': return Document;
    case 'element': return Folder;
    case 'object': return Folder;
    case 'array': return Files;
    case 'value': return CollectionTag;
    case 'comment': return ChatDotSquare;
    case 'functionCall': return Cpu;
    case 'variableReference': return DataLine;
    case 'memberExpression': return Share;
    case 'binaryExpression': return Operation;
    case 'unaryExpression': return Operation;
    case 'conditionalExpression': return Switch;
    case 'templateLiteral': return Tickets;
    case 'newExpression': return RefreshRight;
    default: return QuestionFilled;
  }
});

const nodeTypeClass = computed(() => `node-type-${props.data.nodeSemanticType}`);

const nodeDisplayName = computed(() => {
  const name = props.data.name;
  const type = props.data.nodeSemanticType;
  const format = props.data.format;
  const displayMode = visualizerSettings.xmlTagNameDisplayMode;

  // Apply XML tag display mode if applicable
  if (name && format === 'xml' && type === 'element') {
    if (displayMode === 'openingTag') {
      return `<${name}>`;
    } else if (displayMode === 'closingTag') {
      return `</${name}>`;
    } // else 'nameOnly' or default
    return name;
  }

  // Fallback for non-XML elements or other types
  if (name) return name;
  if (type === 'array') return '[Array]';
  if (type === 'object') return '{Object}';
  if (type === 'comment') return 'Comment';
  return `[${type || 'Unknown'}]`;
});

const nodeBackgroundColor = computed<string>(() => {
  const mode = visualizerSettings.coloringMode;
  const type = props.data.nodeSemanticType;
  const name = props.data.name;
  const level = props.data.level ?? 0;
  const format = props.data.format;

  const fallbackColor = defaultElementObjectColor;

  // Helper for level adjustment
  const adjustColorByLevel = (baseColor: string, currentLevel: number): string => {
    try {
      const lightenAmount = Math.min(currentLevel * 3, 30);
      return tinycolor(baseColor).lighten(lightenAmount).toString();
    } catch (e) {
      console.error("Error adjusting color by level:", e);
      return baseColor;
    }
  };

  // Helper to check JDF keywords
  const getJdfKeywordColor = (): string | undefined => {
    if (name && (format === 'xml' || format === 'unknown')) {
      return jdfCoreTagColorCodes[name];
    }
    return undefined;
  };

  // Placeholder Helper for Metron keywords (returns undefined for now)
  const getMetronKeywordColor = (): string | undefined => {
    // TODO: Implement fuzzy matching logic here later
    // For now, always return undefined so it falls back
    // Example structure:
    // if (name && checkMetronKeywordMatch(name)) {
    //   return metronKeywordColorCodes[getMatchedKeyword(name)];
    // }
    return undefined;
  };

  switch (mode) {
    case 'semantic':
      return semanticTypeColorCodes[type] || fallbackColor;

    case 'level':
      if (levelColorPalette.length > 0) {
        const colorIndex = level % levelColorPalette.length;
        return levelColorPalette[colorIndex];
      } else {
        return fallbackColor;
      }

    case 'single-structure':
      if (type === 'element' || type === 'object') {
        return visualizerSettings.singleStructureColorValue || defaultElementObjectColor;
      }
      return semanticTypeColorCodes[type] || defaultElementObjectColor;

    case 'single-true':
      if (type === 'root' && semanticTypeColorCodes['root']) {
        return semanticTypeColorCodes['root'];
      }
      return visualizerSettings.trueSingleColorValue || '#d1d5db';

    case 'jdf-keyword':
      if (type === 'element' || type === 'object') {
        return getJdfKeywordColor() || defaultElementObjectColor;
      }
      return semanticTypeColorCodes[type] || fallbackColor;

    case 'jdf-keyword-level':
      if (type === 'element' || type === 'object') {
        const baseColor = getJdfKeywordColor() || defaultElementObjectColor;
        return adjustColorByLevel(baseColor, level);
      }
      return semanticTypeColorCodes[type] || fallbackColor;

    case 'metron-keyword':
      if (type === 'element' || type === 'object') {
        return defaultElementObjectColor;
      }
      return semanticTypeColorCodes[type] || defaultElementObjectColor;

    case 'metron-keyword-level':
      if (type === 'element' || type === 'object') {
        const baseColor = defaultElementObjectColor;
        return adjustColorByLevel(baseColor, level);
      }
      return semanticTypeColorCodes[type] || fallbackColor;

    case 'custom-schema':
      // Placeholder: Fallback to default (jdf-keyword-level) for now
      // [[ Fallthrough ]]

    default:
      // Default is jdf-keyword-level
      if (type === 'element' || type === 'object') {
        const baseColor = getJdfKeywordColor() || defaultElementObjectColor;
        return adjustColorByLevel(baseColor, level);
      } else {
        return semanticTypeColorCodes[type] || fallbackColor;
      }
  }
});

const contrastingTextColor = computed<string>(() => {
    const bgColor = nodeBackgroundColor.value;
    if (!bgColor) return '#1f2937';
    try {
        return tinycolor(bgColor).isLight() ? '#1f2937' : 'white';
    } catch (e) {
        console.error("Error determining text color:", e);
        return '#1f2937';
    }
});

const attributeCount = computed(() => {
    if (!props.data.attributes) return 0;
    // Ensure attributes is an object before getting keys
    return typeof props.data.attributes === 'object' && props.data.attributes !== null
           ? Object.keys(props.data.attributes).length
           : 0;
});

const canShowAttributes = computed(() => {
  // Only show attributes for element/object types that actually have attributes
  return (props.data.nodeSemanticType === 'element' || props.data.nodeSemanticType === 'object') && attributeCount.value > 0;
});

const shouldRenderAttributeList = computed(() => {
  const mode = visualizerSettings.attributeDisplayMode;
  // Return true only if the mode requires rendering the list itself
  return mode === 'first-1' || mode === 'first-3' || mode === 'all';
});

const attributesToDisplay = computed(() => {
  if (!canShowAttributes.value || !props.data.attributes) return [];

  const attrs = Object.entries(props.data.attributes)
                    .map(([name, value]) => ({ name, value: String(value) })); // Ensure value is string

  const mode = visualizerSettings.attributeDisplayMode;
  if (mode === 'first-1') return attrs.slice(0, 1);
  if (mode === 'first-3') return attrs.slice(0, 3);
  if (mode === 'all') return attrs; // No slicing needed for 'all' initially, truncation handles length
  return [];
});

const hasMoreAttributes = computed(() => {
  if (!canShowAttributes.value) return false;
  const mode = visualizerSettings.attributeDisplayMode;
  return (mode === 'first-1' && attributeCount.value > 1) ||
         (mode === 'first-3' && attributeCount.value > 3);
});

const truncate = (text: string, maxLength: number): string => {
  return text.length > maxLength ? text.substring(0, maxLength - 1) + '…' : text;
};

const attributeSeparatorStyle = computed<CSSProperties>(() => {
  const style: CSSProperties = {};
  const separatorStyle = visualizerSettings.attributeSeparatorStyle;

  if (separatorStyle === 'none') {
    style.borderTopWidth = '0px';
    style.borderTopStyle = 'none';
  } else {
    style.borderTopWidth = '1px';
    style.borderTopStyle = separatorStyle;
    style.borderTopColor = 'var(--el-border-color)';
  }
  return style;
});

const nodeComputedStyles = computed<CSSProperties>(() => {
  const styles: CSSProperties = {
    backgroundColor: nodeBackgroundColor.value,
    color: contrastingTextColor.value,
    // Apply border here now to avoid affecting handles
    border: `1px solid ${props.selected ? 'var(--el-color-primary-dark-2)' : 'rgba(0, 0, 0, 0.1)'}`,
    // Add dark mode border adjustment
    // Consider adding a helper function or accessing theme from a composable for cleaner dark mode checks
    // For now, a simple check on a known dark mode class might suffice if one exists on a parent
    // borderColor: isDarkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)',
  };
  return styles;
});

const handleShowDetails = () => {
  console.log("[UniversalNode] Show details clicked for node:", props.id);
  editorStore.selectNodeForDrawer(props.id);
};

const handleDeleteNode = async () => {
    console.log(`[UniversalNode] Delete requested for node: ${props.id}`);
    try {
        // ADD CONFIRMATION DIALOG
        await ElMessageBox.confirm(
            `确定要删除节点 "${props.data.name || props.id}" 及其所有子节点吗？此操作不可逆。`,
            '确认删除',
            {
                confirmButtonText: '确定删除',
                cancelButtonText: '取消',
                type: 'warning',
            }
        );
        // If confirmed, proceed with deletion
        await editorStore.deleteNodeWithDescendants(props.id);
        ElMessage.success('节点已删除。'); // Provide feedback
    } catch (error) { 
        if (error !== 'cancel' && error !== 'close') {
            console.error("Error deleting node from UniversalNode:", error);
            ElMessage.error(`删除节点失败: ${editorStore.error || '未知错误'}`);
        } else {
            ElMessage.info('删除操作已取消。');
        }
    }
};

</script>

<style lang="scss" scoped>
.universal-node {
  // Remove border, background, color from the main container
  // border: 1px solid rgba(0, 0, 0, 0.1); 
  // background-color and color set by inline styles on .node-content
  padding: 0; // Handles should touch the edge
  border-radius: 6px;
  // display: flex; // No longer needed here
  // align-items: center;
  // min-width: 150px; // Apply min-width to content if needed
  font-size: 13px;
  position: relative; // Keep for Handle positioning
  transition: box-shadow 0.2s ease;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  overflow: visible; // Ensure handles are not clipped

  &.is-selected {
    // Apply selection shadow to the outer container
    box-shadow: 0 0 0 2px var(--el-color-primary);
  }

  .node-content {
    // Apply visual styles here now
    display: flex;
    align-items: center;
    gap: 5px; // Space between elements
    flex-wrap: wrap; /* Allow items to wrap */
    padding: 8px 12px; // Restore padding here
    border-radius: 6px; // Keep border radius
    min-width: 150px;
    position: relative; // For absolute positioning within content if needed
    box-sizing: border-box; // Ensure padding and border are included in the element's total width and height
    // background-color, color, border are set via :style="nodeComputedStyles"
  }

  .node-icon {
    flex-shrink: 0; // Prevent icon from shrinking
    display: flex; // Center icon if needed
    align-items: center;
  }

  .node-label {
    font-weight: 500;
    white-space: nowrap; // Prevent wrapping
    overflow: hidden;
    text-overflow: ellipsis; // Add ellipsis if too long
    max-width: 100px; // Limit label width
  }

  .node-label strong {
    // Text color is now dynamic, maybe remove fixed white? Or adjust based on contrast?
    // color: white; 
  }
  .node-label span {
      opacity: 0.85;
  }

  .separator {
    // Use contrasting text color logic
    // color: rgba(255, 255, 255, 0.7);
    opacity: 0.7;
    margin: 0 4px;
  }

  .node-value {
    // Use contrasting text color logic
    // color: rgba(255, 255, 255, 0.9);
    opacity: 0.9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex-grow: 1;
    max-width: 150px;
  }

  .attribute-display-wrapper {
    width: 100%;
    padding-top: 6px;
    margin-top: 6px;
    line-height: 1.4;
    box-sizing: border-box;
    /* Dynamic border applied via :style */

    &.normalize-height {
       min-height: 18px;
       // Add a faint background in debug/dev to see the reserved space?
       // background-color: rgba(255,0,0,0.1); 
    }
  }

  .attribute-summary {
    font-size: 0.9em;
    opacity: 0.8;
  }

  .attribute-list {
    display: flex; /* Stack items vertically */
    flex-direction: column;
    gap: 2px; /* Vertical gap */
    align-items: flex-start;
    width: 100%;
  }

  .attribute-item {
    display: flex; /* Keep name/value inline */
    font-size: 0.9em;
    width: 100%;
    max-width: 100%;
    overflow: hidden;

    .attr-name {
      font-weight: 600;
      margin-right: 4px;
      color: var(--el-color-success);
      flex-shrink: 0;
      &::after {
        content: '=';
        margin-left: 2px;
        color: var(--el-text-color-regular);
        opacity: 0.8;
      }
    }

    .attr-value {
      color: var(--el-color-danger);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      opacity: 0.85;
      flex-grow: 1;
      text-align: left;
    }
  }

  .more-attrs {
    font-size: 0.9em;
    opacity: 0.8;
    margin-top: 2px;
  }

  // Style the handles (adjusted for potentially different positions)
  :deep(.vue-flow__handle) {
    width: 8px;
    height: 10px;
    background-color: rgba(0, 0, 0, 0.2);
    border: 1px solid rgba(0, 0, 0, 0.3);
    z-index: 10; // Ensure handles are above content
  }
  // Adjust handle offset slightly if needed based on new positions
  :deep(.vue-flow__handle-left) { left: -4px; }
  :deep(.vue-flow__handle-right) { right: -4px; }
  :deep(.vue-flow__handle-top) { top: -5px; }
  :deep(.vue-flow__handle-bottom) { bottom: -5px; }

  :deep(.vue-flow__handle-connecting) {
    background-color: var(--el-color-primary);
  }
  :deep(.vue-flow__handle-valid) {
    background-color: var(--el-color-success);
  }
}

// Dark mode adjustments
.dark .universal-node {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);

  &.is-selected {
    box-shadow: 0 0 0 2px var(--el-color-primary-light-3);
  }

  .node-content {
    // Adjust dark mode border if nodeComputedStyles doesn't handle it
    // border-color: rgba(255, 255, 255, 0.15);
  }

  :deep(.vue-flow__handle) {
    background-color: rgba(255, 255, 255, 0.3);
    border-color: rgba(255, 255, 255, 0.4);
  }
}

// Dark mode adjustments for attributes
.dark .attribute-display-wrapper {
  /* Adjust opacity if needed */
}
</style>